 /* 
  Enunciado:
Comprimir un archivo
TpGrupo13 -c pepe
Comprime el archivo pepe y genera pepe.13 (13 es el numero de grupo)
TpGrupo13 -d pepe.13
Descomprime pepe.13 y genera el archivo pepe 
Los archivos pueden ser de cualquier tipo y cualquier extensión o sin extensión.
Por ej TpGrupo13 -c pepe.doc genera pepe.doc.13

 */

#include <iostream> 
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <algorithm>
#include <stack> 
//#include <iomanip> // en imprimirArbol setw
#include <queue> // imprimirArbol
#include <utility>   // for std::pair

#include "bwtC.h"
#include "bwrD.h"
#include "Alfabeto.h"
#include "mtfM.h"
#include "mtfW.h"
#include "preprocesamiento.h"
#include "huffman.h"
#include "BitPrinter.h"


#define SIZE_BLOQUE 1024*1064
#define TAMANIO 255 
#define TAM_VFREC 256
#define OK 0
#define ERROR -1
#define EXTENSION_COMPRIMIDO ".10" //el archivo comprimido se llama pepe.doc.13 siendo 13 el nro de grupo
#define EXTENSION_DUPLICADO "(copy)"

using namespace std;

//buffer que va levantando los bloques del archivo a comprimir
unsigned char buffer[(SIZE_BLOQUE+1)]={0};


//void imprimirArbol(const nodoArbol *n, ostream& file) {
//    if (nullptr == n) {
//        return;
//    } 
//    int level = 0;
//
//    // Use a queue for breadth-first traversal of the tree.  The pair is
//    // to keep track of the depth of each node.  (Depth of root node is 1.)
//    typedef pair<const nodoArbol*,int> node_level;
//    queue<node_level> q;
//    q.push(node_level(n, 1));
//
//    while (!q.empty()) {
//        node_level nl = q.front();
//        q.pop();
//        if (nullptr != (n = nl.first)) {
//            if (level != nl.second) {
//                file << endl << " Level " << nl.second << ": " ;
//                level = nl.second;
//            }
//            file << n->dato.frec ;
//            
//            if(n->h_izq == NULL && n->h_der == NULL){
//                file << "/"<< (unsigned short)n->dato.ch  <<  ' ';
//            }
//            else{
//                file <<  ' ';
//            }
//            
//            q.push(node_level(n->h_izq,  1 + level));
//            q.push(node_level(n->h_der, 1 + level));
//        }
//    }
//    file << endl;
//}

int imprimirTablaFrecuencias(ostream& archivo_comprimido,vFrecuencias_t* vFrecuencias){
    
    int i = 0;
    for (i=0;i<TAM_VFREC;i++){
        archivo_comprimido.write((char*)&vFrecuencias[i].frec,sizeof(unsigned int));
    }
    
}

int leerTablaFrecuencias(istream& file,vFrecuencias_t* vFrecuencias){
    
    unsigned int i = 0;
    for (i=0;i<TAM_VFREC;i++){
        vFrecuencias[i].ch = (unsigned char)i;
        file.read((char*)&vFrecuencias[i].frec,sizeof(unsigned int));
    }
    
}

int comprimir(istream &archivo_a_comprimir, string nombre_destino ){
    
    cout << "Comprimiendo... "  << endl;
//    ofstream log("log.txt",ios:: trunc | ios:: out);
    
    /*#################### SECCION ARMADO DEL ALFABETO ####################*/
    
    Alfabeto alf;
    short tipoAlf = 0;
    unsigned char cadenaAlf[TAMANIO+1];        
    
    //Abro y cierro el archivo de destino con trunc, que borra lo que tenga a dentro si tiene algo
//    ofstream archivo_comprimido(nombre_destino.c_str(), ios::trunc);      
       
//    
//    archivo_comprimido.close();    
    
    //Abro el archivo a escribir
    ofstream archivo_comprimido(nombre_destino.c_str(), ios::out | ios::trunc | ios::binary);        
        
    if (!archivo_comprimido){        
        cerr << "Error al abrir el archivo de destino " << endl;
        return ERROR; 
    }
    
    //Armo el alfabeto
    tipoAlf = armarAlf(archivo_a_comprimir,alf,cadenaAlf);
//    
//    Use esto para imprimir el alfabeto default
//    for(int c=0; c<256; c++) {
//        printf("alfEnCadena[%i] = %d ; // %c \n",c-1, cadenaAlf[c], cadenaAlf[c]);
//        
//    }
    
    if(tipoAlf==0){
        cout << "Main - Alfabeto default " << endl;        
        archivo_comprimido.write((char*)&tipoAlf,sizeof(short));
    }
    if(tipoAlf>0) {                                
        cout << "Main - Alfabeto nuevo " <<endl;
        //OJO aca ver "cadenaAlf[256]" problemas con el \0
        archivo_comprimido.write((char*)&tipoAlf,sizeof(short));
        archivo_comprimido.write((char*)cadenaAlf,sizeof(unsigned char)*(tipoAlf));
//        log << "Alfabeto nuevo: ";
//        for (int f=0 ; f < 256; f++){
//            log << (unsigned short) cadenaAlf[f] << " "<<endl;
//        }
//        log << endl;
    }
    if(tipoAlf<0){
        cerr << "Error al armar el alfabeto " << endl;
        return ERROR;
    }
    
    
    archivo_a_comprimir.clear();
    archivo_a_comprimir.seekg(0, ios::beg);
    
    /*#################### SECCION BLOCK SORTING ####################*/
    

    unsigned int indiceI = 0; 
    unsigned int tamBloqueLeido = 0; //Ojo esto, hay que ver si entra en un int 
    nodoArbol* raiz;
    
   // ofstream logTree("logTree.txt",ios:: trunc | ios:: out);
//    ofstream logCodigos("logCodigos.txt",ios:: trunc | ios:: out);
    
//    cout << "Block Sorting" << endl;   
    
    
    //Inicializa el vector de frecuencias con todos los caracteres 
    //asignandoles frecuencia 1
    vFrecuencias_t vFrecuencias[TAM_VFREC];
           
    
    while(!archivo_a_comprimir.eof()){
        
        iniciar_vFrecuencias(vFrecuencias ,TAM_VFREC);
        
        cout << "."<< flush;
        
        archivo_a_comprimir.read((char*)buffer,sizeof(char)*SIZE_BLOQUE);

        tamBloqueLeido = archivo_a_comprimir.gcount(); 
        
        
        
        buffer[tamBloqueLeido] = '\0';
       
        unsigned char stringL[tamBloqueLeido+1];
        unsigned char stringR[tamBloqueLeido+1];
        
        bwt_algoritmoC(buffer,tamBloqueLeido,stringL,indiceI);
       
//        log << endl << "Bloque de tamaño: "<< tamBloqueLeido << endl;
//        log << "Indice: " << indiceI << endl;
        

        mtf_algoritmoM(stringL,stringR,tamBloqueLeido,alf);
        
        archivo_comprimido.write((char*)&indiceI,sizeof(unsigned int));
      
        /*por como se hizo imprimir_codigo, aca hay que guardar en el archivo 
         * comprimido el tamBloqueLeido, que segun el SIZE_BLOQUE que vayamos
         * a usar hay que ver de que tipo de dato es.
         * Es necesario para saber cuando parar de leer bits al descomprimir.         
         */
        archivo_comprimido.write((char*)&tamBloqueLeido,sizeof(unsigned int));
       
        BitPrinter bit;
  
//        logCodigos << endl << "Bloque" << endl;
        
        
        // ESTÁTICO: Primer for, vacié todo menos el actualizar frecuencias
        
        for (unsigned int i=0;i<tamBloqueLeido;i++){
                        
            actualizarFrecuencias(vFrecuencias,stringR[i]);
        }
        
        imprimirTablaFrecuencias(archivo_comprimido,vFrecuencias);
        
//        log << "vFrec leido: "<<  endl;
//        
//        for (int g = 0 ; g < TAM_VFREC; g++){
//            log << "vFrec["<< g << "]= " << (unsigned short)vFrecuencias[g].ch << " - " << vFrecuencias[g].frec << endl;            
//         }
//        
//        log << endl;
        
        ordenarVFrecuencias(vFrecuencias);
        
//        log << "vFrec ordenado: " <<  endl;
//        
//                for (int g = 0 ; g < TAM_VFREC; g++){
//            log << "vFrec["<< g << "]= " << (unsigned short)vFrecuencias[g].ch << " - " << vFrecuencias[g].frec << endl;            
//         }
//        
//        log << endl;
        
        //Crea el árbol de huffman con el vector de frecuencias indicado. 
        raiz = armar_arbol(vFrecuencias, TAM_VFREC); 
        if (!raiz) cout << "ERROR ARMANDO EL ARBOL" << endl;

        //imprimirArbol(raiz,logTree);           

        stack<bool> pila;

        // ESTATICO: 2do for, igual al original excepto armar_árbol y destruir_arbol 
        // quedaron afuera
        for (unsigned int i=0;i<tamBloqueLeido;i++){ 


            codificar_caracter(raiz, &pila, stringR[i]);                         

            while(!pila.empty()){

                bool codigoBit = pila.top();

                if(codigoBit == 1){
                    bit.printTRUE(archivo_comprimido);
//                    logCodigos << "1" ;
                }
                else{
                    bit.printFALSE(archivo_comprimido);
//                    logCodigos << "0";
                }

                pila.pop();
            }

//            logCodigos << " "<< (unsigned short)stringR[i] << endl; 

        }
        destruir_arbol(raiz); 

        //tengo que cerrar para escribir en el archivo lo que quedo colgado en el buffer
        bit.close(archivo_comprimido);
        
        //BORRAR archivo_comprimido.write((char*)stringR,sizeof(unsigned char)*tamBloqueLeido);                        
              
    }
    
    cout << endl;
    
    archivo_a_comprimir.clear();
    
    long fszin = archivo_a_comprimir.tellg();
    long fszout = archivo_comprimido.tellp();
    printf("Tamanio del archivo original: %ld bytes\n", fszin);
    printf("Tamanio del archivo comprimido: %ld bytes\n", fszout);
    float pct = (1 - ((float)fszout/(float)fszin)) * 100.0;
    printf("Nivel de compresion: %.2f%%\n", pct);
    
    //Cierro el archivo comprimido
    archivo_comprimido.close();
//    log.close();
 //   logTree.close();
//    logCodigos.close();
//    logNodos.close();
      
    return OK;
}

int descomprimir(istream &archivo_a_descomprimir, string nombre_destino ){
    
    cout << "Descomprimiendo..."<<endl;
    
//    ofstream log("logDe.txt",ios:: trunc | ios:: out);
    
    /*#################### SECCION ARMADO DEL ALFABETO ####################*/
    
    Alfabeto alf;        
    
    recuperarAlf(archivo_a_descomprimir,alf);  
    
//        log << "Alfabeto nuevo: ";
//        unsigned char cha;
//        
//        for (int f=0 ; f < 256; f++){
//            alf.obtener_char(f,&cha);
//            log << (unsigned short)cha << " " <<endl;
//        }
//        log << endl;
    
    /*#################### SECCION BLOCK SORTING ####################*/
    
    ofstream archivo_salida;

    unsigned int indice = 0;
    unsigned int tamBloqueALeer = 0;
    nodoArbol* raizd;
    nodoArbol* nodo;
    
//    ofstream logTree("logTreeDe.txt",ios:: trunc | ios:: out);
//    ofstream logCodigos("logCodigosDe.txt",ios:: trunc | ios:: out);
       
    //Abro y cierro el archivo con trunc para que se reseteen los datos si existe
//    archivo_salida.open(nombre_destino.c_str(),ios::trunc);
//    if (!archivo_salida) cerr << " Main - descomprimir() - No se puede crear el nuevo archivo " << endl; 
//    archivo_salida.close();
    
    //Abro para escribir
    archivo_salida.open(nombre_destino.c_str(),ios::out | ios::trunc | ios::binary);
    if (!archivo_salida) cerr << " Main - descomprimir() - No se puede abrir el nuevo archivo " << endl; 
    
    //Inicializa el vector de frecuencias con todos los caracteres 
    //asignandoles frecuencia 1
    vFrecuencias_t vFrecuencias[TAM_VFREC];
    iniciar_vFrecuencias(vFrecuencias ,TAM_VFREC); 
    
   
    
    while(archivo_a_descomprimir.read((char*)&indice,sizeof(unsigned int))){
        
        cout << ","<< flush;
        
        archivo_a_descomprimir.read((char*)&tamBloqueALeer,sizeof(unsigned int));
        
        
//        log << endl << "Bloque de tamaño: "<< tamBloqueALeer << endl;
//        log << "Indice: " << indice << endl;
        
         leerTablaFrecuencias(archivo_a_descomprimir, vFrecuencias);
         
//        log << "vFrec leido: "<<  endl;
//        
//        for (int g = 0 ; g < TAM_VFREC; g++){
//            log << "vFrec["<< g << "]= " << (unsigned short)vFrecuencias[g].ch << " - " << vFrecuencias[g].frec << endl;            
//         }
//        
//        log << endl;
        
        ordenarVFrecuencias(vFrecuencias);
        
//        log << "vFrec ordenado: " <<  endl;
//        
//                for (int g = 0 ; g < TAM_VFREC; g++){
//            log << "vFrec["<< g << "]= " << (unsigned short)vFrecuencias[g].ch << " - " << vFrecuencias[g].frec << endl;            
//         }
//         log << endl;
        
//        

        
        unsigned char stringR[tamBloqueALeer+1];  
        unsigned char stringL[tamBloqueALeer +1];
        unsigned char S[tamBloqueALeer+1];
    
        //archivo_a_descomprimir.read((char*)stringR,SIZE_BLOQUE* sizeof(unsigned char));        
        //tamBloqueLeido = archivo_a_descomprimir.gcount();                
        
        unsigned int charCounter = 0;
        
        BitReader bit;  
        
//        logCodigos << endl << "Bloque " << endl;
        raizd = armar_arbol(vFrecuencias, TAM_VFREC);   
        
        while(charCounter!=tamBloqueALeer){
                 
            //Crea el árbol de huffman con el vector de frecuencias indicado. 
            
            
//            imprimirArbol(raizd,logTree);
            
            nodo = raizd;
            
            bool codigoBit;
            
            while(nodo->h_der && nodo->h_izq){

                codigoBit = bit.readFromFile(archivo_a_descomprimir);

                if(codigoBit == 1){
                    nodo = nodo ->h_der;
//                    logCodigos << "1" ;
                }
                
                if(codigoBit == 0){
                    nodo = nodo ->h_izq;
//                    logCodigos << "0";
                }                           
            }
            
            stringR[charCounter]=nodo->dato.ch;
            
//            logCodigos << " " << (unsigned int)stringR[charCounter] << endl;

            
            
            //old dinamico actualizarFrecuencias(vFrecuencias,stringR[charCounter]);
            
            charCounter ++;
        }
        
        destruir_arbol(raizd); 
        
        mtf_algoritmoW(stringL,stringR,tamBloqueALeer,alf);
            
        bwt_algoritmoD(S,tamBloqueALeer,stringL,indice); 
        
        archivo_salida.write((char*)S,tamBloqueALeer*sizeof(unsigned char));
        
   
    
    }//while
    
    archivo_salida.close();
//    log.close();
//    logTree.close();
//    logCodigos.close();
//    
    return OK;
}

int main(int argc, char**argv) {
    
    char * nombre_del_archivo;
   
    ifstream archivo_entrada;
    
    /* ######################################################################## 
     * SIN ARGUMENTOS Procedimiento por default: 
     * toma el archivo a comprimir desde y hasta:
     * #######################################################################*/
    
    
    if (argc == 1) {
        
        cout << endl << "Main - Parametros: default (comprime y descomprime) documento.txt" << endl;
             
        nombre_del_archivo = "calgary/paper5";
        archivo_entrada.open(nombre_del_archivo,ios::in);
        
        //Armo el nombre del archivo comprimido (Ej: documento.txt - documento.txt.10)
        string nombre_del_comprimido = string(nombre_del_archivo) + string(EXTENSION_COMPRIMIDO);                
        
        //Armar el nombre del nuevo archivo        
        string nombre_del_descomprimido = string(nombre_del_archivo);        
        unsigned finNombre = nombre_del_descomprimido.find_last_of(".");
        if(finNombre==-1/*nombre_del_descomprimido.npos*/){
            //El nombre no lleva extension
            nombre_del_descomprimido = nombre_del_descomprimido + string(EXTENSION_DUPLICADO);
        }            
        else {
            //El nombre tiene extensión, se inserta el (copy) antes
            nombre_del_descomprimido.insert(finNombre,EXTENSION_DUPLICADO);
        }    
        

        if(!archivo_entrada){
            
            cerr << "No se pudo abrir el archivo" << endl;
            return ERROR;
            
        }
        else{
                                   
            /* ########################################################################## 
            * ########################### COMPRESION ####################################
            * ##########################################################################*/                        
            
            comprimir(archivo_entrada,nombre_del_comprimido);      
            
            archivo_entrada.close();
            
        }
        
            /* ########################################################################## 
            * ########################### DESCOMPRESION #################################
            * ##########################################################################*/
            
          
        ifstream archivo_a_descomprimir(nombre_del_comprimido.c_str(),ios::in);
            
        if (!archivo_a_descomprimir){
                
            cerr << "No se pudo abrir el archivo a descomprimir" << endl;
                
        }
        else{                               
                descomprimir(archivo_a_descomprimir,nombre_del_descomprimido);
                archivo_a_descomprimir.close();
                cout << "Todo ok grosos!"<< endl;
        }
            
        return OK;
                   
    }
    
     /* ########################################################################## 
     * CON ARGUMENTOS (-c comprime / -d descomprime)
     * ##########################################################################*/
    
    
    else {
        if (argc != 3 ){
            cout << "Cantidad de parametros incorrecta" << argc << endl;
            return ERROR;
        }
        if (argc == 3) {                                 
                        
            if ( strcmp(argv[1], "-c") == 0 ) {      
                
                /* ########################################################################## 
                * ########################### COMPRESION ####################################
                * ##########################################################################*/
                
                nombre_del_archivo = argv[2];
                
                archivo_entrada.open(nombre_del_archivo,ios::in);
                
                if (!archivo_entrada){
                    cout << endl << "No se pudo leer el archivo" << endl;
                    return ERROR;
                };                                   
                
                string nombre_del_comprimido = string(nombre_del_archivo) + string(EXTENSION_COMPRIMIDO);
                
                if( comprimir(archivo_entrada,nombre_del_comprimido)==OK ) {
                    cout << endl << "El proceso finalizo correctamente" << endl;
                    archivo_entrada.close();
                }else{
                    cout << endl << "Fallo la compresion" << endl;
                    return ERROR;
                };
            }
            
            else if ( strcmp(argv[1], "-d") == 0 ){
                
                 /* ########################################################################## 
                  * ########################### DESCOMPRESION #################################
                  * ##########################################################################*/        
                
                nombre_del_archivo = argv[2];                                
                                                
                //Armar el nombre del nuevo archivo        
                string nombre_del_descomprimido = string(nombre_del_archivo);
                nombre_del_descomprimido = nombre_del_descomprimido.substr(0,nombre_del_descomprimido.size()-3);                                
                unsigned finNombre = nombre_del_descomprimido.find_last_of(".");
                if(finNombre==-1/*nombre_del_descomprimido.npos*/){
                    //El nombre no lleva extension
                    nombre_del_descomprimido = nombre_del_descomprimido + string(EXTENSION_DUPLICADO);
                }            
                else {
                    //El nombre tiene extensión, se inserta el (copy) antes
                    nombre_del_descomprimido.insert(finNombre,EXTENSION_DUPLICADO);
                }    
                
                ifstream archivo_a_descomprimir(nombre_del_archivo,ios::in);
            
                if (!archivo_a_descomprimir){                    
                cerr << "No se pudo abrir el archivo a descomprimir" << endl;
                return ERROR;
                }                
                
                if(descomprimir(archivo_a_descomprimir,nombre_del_descomprimido)==OK){
                    cout << endl << "El proceso finalizo correctamente" << endl;
                    archivo_a_descomprimir.close();  
                    return OK;
                }
                else{
                    cout << endl << "Fallo la descompresion" << endl;
                    return ERROR;
                }
                                
            }else{
                cout << endl << "Parametros incorrectos" << endl;
                return ERROR;
            }
             
        }
    }    
    
    return OK;
}
